package com.dingreading.cloud.gateway.filters;

import com.dingreading.cloud.common.base.R;
import com.dingreading.cloud.common.dto.LoginDataDto;
import com.dingreading.cloud.common.util.*;
import com.dingreading.cloud.gateway.service.AppProjectService;
import com.dingreading.cloud.gateway.util.MonoUtil;
import com.dingreading.cloud.gateway.util.redis.RedisService;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/***
 * 门店员工鉴权
 */
@Component
public class StoreAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<StoreAuthGatewayFilterFactory.Config> implements Ordered {

    @Resource
    private RedisService redisService;
    @Resource
    private AppProjectService appProjectService;

    public static StoreAuthGatewayFilterFactory util;

    @PostConstruct
    public void init() {
        util = this;
    }


    public StoreAuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("value");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpResponse response = exchange.getResponse();
            ServerHttpRequest request = exchange.getRequest();

            // 登录验证
            Mono<Void> loginAuth = loginAuth(request, response);
            if (loginAuth != null)
                return loginAuth;

            return chain.filter(exchange);
        };
    }

    @Override
    public int getOrder() {
        return 1;
    }

    public static class Config {
        private String value;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

    private final ConcurrentHashMap<String, ExpiringMap<String, Integer>> concurrentHashMap = new ConcurrentHashMap<>();

    /**
     * 登录验证
     */
    private Mono<Void> loginAuth(ServerHttpRequest request, ServerHttpResponse response) {
        // 获取路由的唯一标识
//        String routeId = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR);

        URI uri = request.getURI();
        String path = "";
        if (uri != null) {
            // /front/course/v2/getCourseList
            path = uri.getPath();
            // 跳过验证：验证用户信息、支付回调、redis、swagger
            if (path.contains("/userToken") || path.contains("/payCallback") || path.contains("/redis") || path.contains("/api-docs"))
                return null;
        }

        // ip请求次数限制
        String ip = IpUtil.getIP(request);
//        ExpiringMap<String, Integer> expiringMap = concurrentHashMap.getOrDefault(path, ExpiringMap.builder().variableExpiration().build());
//        Integer uCount = expiringMap.getOrDefault(ip, 0);
//        int limitNum = 100;
//        // 如果是下载或是导出接口，则一分钟限制请求3次
//        if (path.contains("/getDownloadUrl"))
//            limitNum = 3;
//        if (uCount >= limitNum) {
//            // 超过次数，不执行目标方法
//            return MonoUtil.writeResponse(response, 500, "访问次数过多，1分钟后再试");
//        } else if (uCount == 0) {
//            // 第一次请求时，设置有效时间
//            expiringMap.put(ip, uCount + 1, ExpirationPolicy.CREATED, 60000, TimeUnit.MILLISECONDS);
//        } else {
//            // 未超过次数，记录加一
//            expiringMap.put(ip, uCount + 1);
//        }
//        concurrentHashMap.put(path, expiringMap);

        // 网页地址
        String htmlUrl = LoginAuthUtil.getHtmlUrl(request.getHeaders());
        if (StringUtils.isBlank(htmlUrl))
            return MonoUtil.writeResponse(response, ErrorCode.LOGIN_INVALID.getCode(), "网页地址获取失败");

        String appId = LoginAuthUtil.getAppId(htmlUrl);
        if (StringUtils.isBlank(appId))
            return MonoUtil.writeResponse(response, ErrorCode.LOGIN_INVALID.getCode(), "应用Id获取失败");

        String appSecret = getAppSecret(appId);
        if (StringUtils.isBlank(appSecret))
            return MonoUtil.writeResponse(response, ErrorCode.LOGIN_INVALID.getCode(), "应用秘钥获取失败");

        // 获取cookieValue
        String cookieValue = LoginAuthUtil.getCookies(request.getCookies());
        if (StringUtils.isBlank(cookieValue)) {
            // 获取cookieValue失败，去登录
            String loginUrl = LoginAuthUtil.getLoginUrl(appId, appSecret, htmlUrl);
            return MonoUtil.writeResponse(response, ErrorCode.LOGIN_INVALID.getCode(), "令牌校验失败", loginUrl);
        }

        // 根据cookieValue获取redis里的登录信息
        String loginJson = (String) util.redisService.get(RedisParam.STAFF_LOGIN + cookieValue);
        if (StringUtils.isBlank(loginJson)) {
            // 发起请求获取登录信息
            R<Object> r = LoginAuthUtil.getLoginInfo(appId, appSecret, htmlUrl, cookieValue, ip);
            // 获取登录信息失效
            if (R.isError(r))
                return MonoUtil.writeResponse(response, ErrorCode.LOGIN_INVALID.getCode(), r.getMsg(), r.getData());

            LoginDataDto loginDataDto = (LoginDataDto) r.getData();
            // 获取登录信息失效、去登录
            if (loginDataDto == null) {
                String loginUrl = LoginAuthUtil.getLoginUrl(appId, appSecret, htmlUrl);
                return MonoUtil.writeResponse(response, ErrorCode.LOGIN_INVALID.getCode(), "登录用户信息已失效", loginUrl);
            }

            // redis保存用户信息10分钟
            util.redisService.set(RedisParam.STAFF_LOGIN + cookieValue, JsonUtil.toJsonString(loginDataDto), RedisParam.MINUTE_TIME);
        }

        return null;
    }

    /**
     * 获取应用秘钥
     */
    public static String getAppSecret(String appId) {
        String appSecret = (String) util.redisService.get(RedisParam.APP_PROJECT + appId);
        if (StringUtils.isBlank(appSecret)) {
            appSecret = util.appProjectService.getAppSecret(appId);

            if (StringUtils.isNotBlank(appSecret))
                util.redisService.set(RedisParam.APP_PROJECT + appId, appSecret, RedisParam.HOUR_TIME);
        }

        return appSecret;
    }

}
